@@ -1,28 +1,28 @@ |
||
1 | 1 |
module LiquidInterpolatable |
2 | 2 |
extend ActiveSupport::Concern |
3 | 3 |
|
4 |
- def interpolate_options(options, payload = {}) |
|
4 |
+ def interpolate_options(options, event = {}) |
|
5 | 5 |
case options |
6 | 6 |
when String |
7 |
- interpolate_string(options, payload) |
|
7 |
+ interpolate_string(options, event) |
|
8 | 8 |
when ActiveSupport::HashWithIndifferentAccess, Hash |
9 |
- options.inject(ActiveSupport::HashWithIndifferentAccess.new) { |memo, (key, value)| memo[key] = interpolate_options(value, payload); memo } |
|
9 |
+ options.inject(ActiveSupport::HashWithIndifferentAccess.new) { |memo, (key, value)| memo[key] = interpolate_options(value, event); memo } |
|
10 | 10 |
when Array |
11 |
- options.map { |value| interpolate_options(value, payload) } |
|
11 |
+ options.map { |value| interpolate_options(value, event) } |
|
12 | 12 |
else |
13 | 13 |
options |
14 | 14 |
end |
15 | 15 |
end |
16 | 16 |
|
17 |
- def interpolated(payload = {}) |
|
18 |
- key = [options, payload] |
|
17 |
+ def interpolated(event = {}) |
|
18 |
+ key = [options, event] |
|
19 | 19 |
@interpolated_cache ||= {} |
20 |
- @interpolated_cache[key] ||= interpolate_options(options, payload) |
|
20 |
+ @interpolated_cache[key] ||= interpolate_options(options, event) |
|
21 | 21 |
@interpolated_cache[key] |
22 | 22 |
end |
23 | 23 |
|
24 |
- def interpolate_string(string, payload) |
|
25 |
- Liquid::Template.parse(string).render!(payload, registers: {agent: self}) |
|
24 |
+ def interpolate_string(string, event) |
|
25 |
+ Liquid::Template.parse(string).render!(event.to_liquid, registers: {agent: self}) |
|
26 | 26 |
end |
27 | 27 |
|
28 | 28 |
require 'uri' |
@@ -402,5 +402,12 @@ class AgentDrop < Liquid::Drop |
||
402 | 402 |
def to_liquid |
403 | 403 |
AgentDrop.new(self) |
404 | 404 |
end |
405 |
+ |
|
406 |
+ def to_h |
|
407 |
+ drop = to_liquid |
|
408 |
+ AgentDrop.public_instance_methods(false).each_with_object({}) { |attr, hash| |
|
409 |
+ hash[attr.to_s] = drop.__send__(attr) |
|
410 |
+ } |
|
411 |
+ end |
|
405 | 412 |
end |
406 | 413 |
end |
@@ -83,7 +83,7 @@ module Agents |
||
83 | 83 |
def receive_web_request(params, method, format) |
84 | 84 |
if interpolated['secrets'].include?(params['secret']) |
85 | 85 |
items = received_events.order('id desc').limit(events_to_show).map do |event| |
86 |
- interpolated = interpolate_options(options['template']['item'], event.payload) |
|
86 |
+ interpolated = interpolate_options(options['template']['item'], event) |
|
87 | 87 |
interpolated['guid'] = event.id |
88 | 88 |
interpolated['pubDate'] = event.created_at.rfc2822.to_s |
89 | 89 |
interpolated |
@@ -29,7 +29,7 @@ module Agents |
||
29 | 29 |
incoming_events.each do |event| |
30 | 30 |
log "Sending digest mail to #{user.email} with event #{event.id}" |
31 | 31 |
recipients(event.payload).each do |recipient| |
32 |
- SystemMailer.delay.send_message(:to => recipient, :subject => interpolated(event.payload)['subject'], :headline => interpolated(event.payload)['headline'], :groups => [present(event.payload)]) |
|
32 |
+ SystemMailer.delay.send_message(:to => recipient, :subject => interpolated(event)['subject'], :headline => interpolated(event)['headline'], :groups => [present(event.payload)]) |
|
33 | 33 |
end |
34 | 34 |
end |
35 | 35 |
end |
@@ -106,9 +106,8 @@ module Agents |
||
106 | 106 |
|
107 | 107 |
def receive(incoming_events) |
108 | 108 |
incoming_events.each do |event| |
109 |
- agent = Agent.find(event.agent_id) |
|
110 |
- payload = perform_matching({ 'agent' => agent }.update(event.payload)) |
|
111 |
- opts = interpolated(payload) |
|
109 |
+ payload = perform_matching({ 'agent' => event.agent.to_h }.merge(event.payload)) |
|
110 |
+ opts = interpolated(EventDrop.new(event, payload)) |
|
112 | 111 |
formatted_event = opts['mode'].to_s == "merge" ? event.payload.dup : {} |
113 | 112 |
formatted_event.merge! opts['instructions'] |
114 | 113 |
formatted_event['created_at'] = event.created_at unless opts['skip_created_at'].to_s == "true" |
@@ -51,7 +51,7 @@ module Agents |
||
51 | 51 |
message = (event.payload['message'] || event.payload['text']).to_s |
52 | 52 |
subject = event.payload['subject'].to_s |
53 | 53 |
if message.present? && subject.present? |
54 |
- log "Sending Growl notification '#{subject}': '#{message}' to #{interpolated(event.payload)['growl_server']} with event #{event.id}" |
|
54 |
+ log "Sending Growl notification '#{subject}': '#{message}' to #{interpolated(event)['growl_server']} with event #{event.id}" |
|
55 | 55 |
notify_growl(subject,message) |
56 | 56 |
else |
57 | 57 |
log "Event #{event.id} not sent, message and subject expected" |
@@ -59,4 +59,4 @@ module Agents |
||
59 | 59 |
end |
60 | 60 |
end |
61 | 61 |
end |
62 |
-end |
|
62 |
+end |
@@ -42,7 +42,7 @@ module Agents |
||
42 | 42 |
def receive(incoming_events) |
43 | 43 |
client = HipChat::Client.new(interpolated[:auth_token]) |
44 | 44 |
incoming_events.each do |event| |
45 |
- mo = interpolated(event.payload) |
|
45 |
+ mo = interpolated(event) |
|
46 | 46 |
client[mo[:room_name]].send(mo[:username], mo[:message], :notify => mo[:notify].to_s == 'true' ? 1 : 0, :color => mo[:color]) |
47 | 47 |
end |
48 | 48 |
end |
@@ -60,7 +60,7 @@ module Agents |
||
60 | 60 |
end |
61 | 61 |
|
62 | 62 |
def body(event) |
63 |
- interpolated(event.payload)['message'] |
|
63 |
+ interpolated(event)['message'] |
|
64 | 64 |
end |
65 | 65 |
end |
66 | 66 |
end |
@@ -106,7 +106,7 @@ module Agents |
||
106 | 106 |
def receive(incoming_events) |
107 | 107 |
mqtt_client.connect do |c| |
108 | 108 |
incoming_events.each do |event| |
109 |
- c.publish(interpolated(event.payload)['topic'], event.payload) |
|
109 |
+ c.publish(interpolated(event)['topic'], event) |
|
110 | 110 |
end |
111 | 111 |
|
112 | 112 |
c.disconnect |
@@ -136,4 +136,4 @@ module Agents |
||
136 | 136 |
end |
137 | 137 |
|
138 | 138 |
end |
139 |
-end |
|
139 |
+end |
@@ -67,7 +67,7 @@ module Agents |
||
67 | 67 |
if newest_value > average_value + std_multiple * standard_deviation |
68 | 68 |
memory['peaks'][group] << newest_time |
69 | 69 |
memory['peaks'][group].reject! { |p| p <= newest_time - window_duration } |
70 |
- create_event :payload => { 'message' => interpolated(event.payload)['message'], 'peak' => newest_value, 'peak_time' => newest_time, 'grouped_by' => group.to_s } |
|
70 |
+ create_event :payload => { 'message' => interpolated(event)['message'], 'peak' => newest_value, 'peak_time' => newest_time, 'grouped_by' => group.to_s } |
|
71 | 71 |
end |
72 | 72 |
end |
73 | 73 |
end |
@@ -127,4 +127,4 @@ module Agents |
||
127 | 127 |
memory['data'][group].reject! { |value, time| time <= newest_time - window_duration } |
128 | 128 |
end |
129 | 129 |
end |
130 |
-end |
|
130 |
+end |
@@ -68,7 +68,7 @@ module Agents |
||
68 | 68 |
|
69 | 69 |
def receive(incoming_events) |
70 | 70 |
incoming_events.each do |event| |
71 |
- outgoing = interpolated(event.payload)['payload'].presence || {} |
|
71 |
+ outgoing = interpolated(event)['payload'].presence || {} |
|
72 | 72 |
if interpolated['no_merge'].to_s == 'true' |
73 | 73 |
handle outgoing, event.payload |
74 | 74 |
else |
@@ -125,4 +125,4 @@ module Agents |
||
125 | 125 |
Net::HTTP.start(uri.hostname, uri.port, :use_ssl => uri.scheme == "https") { |http| http.request(req) } |
126 | 126 |
end |
127 | 127 |
end |
128 |
-end |
|
128 |
+end |
@@ -49,7 +49,7 @@ module Agents |
||
49 | 49 |
private |
50 | 50 |
|
51 | 51 |
def query_options(event) |
52 |
- mo = interpolated(event.payload) |
|
52 |
+ mo = interpolated(event) |
|
53 | 53 |
{ |
54 | 54 |
:basic_auth => {:username => mo[:api_key], :password => ''}, |
55 | 55 |
:body => {:device_iden => mo[:device_id], :title => mo[:title], :body => mo[:body], :type => 'note'} |
@@ -58,7 +58,7 @@ module Agents |
||
58 | 58 |
|
59 | 59 |
def receive(incoming_events) |
60 | 60 |
incoming_events.each do |event| |
61 |
- payload_interpolated = interpolated(event.payload) |
|
61 |
+ payload_interpolated = interpolated(event) |
|
62 | 62 |
message = (event.payload['message'].presence || event.payload['text'].presence || payload_interpolated['message']).to_s |
63 | 63 |
if message.present? |
64 | 64 |
post_params = { |
@@ -61,7 +61,7 @@ module Agents |
||
61 | 61 |
|
62 | 62 |
def receive(incoming_events) |
63 | 63 |
incoming_events.each do |event| |
64 |
- handle(interpolated(event.payload), event) |
|
64 |
+ handle(interpolated(event), event) |
|
65 | 65 |
end |
66 | 66 |
end |
67 | 67 |
|
@@ -109,4 +109,4 @@ module Agents |
||
109 | 109 |
[result, errors, exit_status] |
110 | 110 |
end |
111 | 111 |
end |
112 |
-end |
|
112 |
+end |
@@ -57,7 +57,7 @@ module Agents |
||
57 | 57 |
|
58 | 58 |
def receive(incoming_events) |
59 | 59 |
incoming_events.each do |event| |
60 |
- opts = interpolated(event.payload) |
|
60 |
+ opts = interpolated(event) |
|
61 | 61 |
slack_notifier.ping opts[:message], channel: opts[:channel], username: opts[:username] |
62 | 62 |
end |
63 | 63 |
end |
@@ -66,7 +66,7 @@ module Agents |
||
66 | 66 |
access_token = JSON.parse(response.body)["access_token"] |
67 | 67 |
incoming_events.each do |event| |
68 | 68 |
translated_event = {} |
69 |
- opts = interpolated(event.payload) |
|
69 |
+ opts = interpolated(event) |
|
70 | 70 |
opts['content'].each_pair do |key, value| |
71 | 71 |
translated_event[key] = translate(value.first, opts['to'], access_token) |
72 | 72 |
end |
@@ -57,7 +57,7 @@ module Agents |
||
57 | 57 |
def receive(incoming_events) |
58 | 58 |
incoming_events.each do |event| |
59 | 59 |
|
60 |
- opts = interpolated(event.payload) |
|
60 |
+ opts = interpolated(event) |
|
61 | 61 |
|
62 | 62 |
match = opts['rules'].all? do |rule| |
63 | 63 |
value_at_path = Utils.value_at(event['payload'], rule['path']) |
@@ -105,4 +105,4 @@ module Agents |
||
105 | 105 |
interpolated['keep_event'] == 'true' |
106 | 106 |
end |
107 | 107 |
end |
108 |
-end |
|
108 |
+end |
@@ -44,13 +44,13 @@ module Agents |
||
44 | 44 |
incoming_events.each do |event| |
45 | 45 |
message = (event.payload['message'].presence || event.payload['text'].presence || event.payload['sms'].presence).to_s |
46 | 46 |
if message.present? |
47 |
- if interpolated(event.payload)['receive_call'].to_s == 'true' |
|
47 |
+ if interpolated(event)['receive_call'].to_s == 'true' |
|
48 | 48 |
secret = SecureRandom.hex 3 |
49 | 49 |
memory['pending_calls'][secret] = message |
50 | 50 |
make_call secret |
51 | 51 |
end |
52 | 52 |
|
53 |
- if interpolated(event.payload)['receive_text'].to_s == 'true' |
|
53 |
+ if interpolated(event)['receive_text'].to_s == 'true' |
|
54 | 54 |
message = message.slice 0..160 |
55 | 55 |
send_message message |
56 | 56 |
end |
@@ -86,4 +86,4 @@ module Agents |
||
86 | 86 |
end |
87 | 87 |
end |
88 | 88 |
end |
89 |
-end |
|
89 |
+end |
@@ -41,7 +41,7 @@ module Agents |
||
41 | 41 |
incoming_events = incoming_events.first(20) |
42 | 42 |
end |
43 | 43 |
incoming_events.each do |event| |
44 |
- tweet_text = interpolated(event.payload)['message'] |
|
44 |
+ tweet_text = interpolated(event)['message'] |
|
45 | 45 |
begin |
46 | 46 |
tweet = publish_tweet tweet_text |
47 | 47 |
create_event :payload => { |
@@ -67,4 +67,4 @@ module Agents |
||
67 | 67 |
twitter.update(text) |
68 | 68 |
end |
69 | 69 |
end |
70 |
-end |
|
70 |
+end |
@@ -47,7 +47,7 @@ module Agents |
||
47 | 47 |
incoming_events = incoming_events.first(20) |
48 | 48 |
end |
49 | 49 |
incoming_events.each do |event| |
50 |
- tweet_text = Utils.value_at(event.payload, interpolated(event.payload)['message_path']) |
|
50 |
+ tweet_text = Utils.value_at(event.payload, interpolated(event)['message_path']) |
|
51 | 51 |
if event.agent.type == "Agents::TwitterUserAgent" |
52 | 52 |
tweet_text = unwrap_tco_urls(tweet_text, event.payload) |
53 | 53 |
end |
@@ -83,4 +83,4 @@ module Agents |
||
83 | 83 |
end |
84 | 84 |
|
85 | 85 |
end |
86 |
-end |
|
86 |
+end |
@@ -41,3 +41,35 @@ class Event < ActiveRecord::Base |
||
41 | 41 |
Agent.receive!(:only_receivers => propagate_ids) unless propagate_ids.empty? |
42 | 42 |
end |
43 | 43 |
end |
44 |
+ |
|
45 |
+class EventDrop < Liquid::Drop |
|
46 |
+ def initialize(event, payload = event.payload) |
|
47 |
+ @event = event |
|
48 |
+ @payload = payload |
|
49 |
+ end |
|
50 |
+ |
|
51 |
+ def before_method(key) |
|
52 |
+ if @payload.key?(key) |
|
53 |
+ @payload[key] |
|
54 |
+ else |
|
55 |
+ case key |
|
56 |
+ when 'agent' |
|
57 |
+ @event.agent |
|
58 |
+ end |
|
59 |
+ end |
|
60 |
+ end |
|
61 |
+ |
|
62 |
+ # Allow iteration using a "for" loop. Including Enumerable will |
|
63 |
+ # enable methods like max, min and sort, but it does not make much |
|
64 |
+ # sense since this is a hash-like object. |
|
65 |
+ def each(&block) |
|
66 |
+ return to_enum(__method__) unless block |
|
67 |
+ @payload.each(&block) |
|
68 |
+ end |
|
69 |
+ |
|
70 |
+ class ::Event |
|
71 |
+ def to_liquid |
|
72 |
+ EventDrop.new(self) |
|
73 |
+ end |
|
74 |
+ end |
|
75 |
+end |
@@ -8,7 +8,8 @@ describe Agents::EventFormattingAgent do |
||
8 | 8 |
:instructions => { |
9 | 9 |
:message => "Received {{content.text}} from {{content.name}} .", |
10 | 10 |
:subject => "Weather looks like {{conditions}} according to the forecast at {{pretty_date.time}}", |
11 |
- :agent => "{{agent.type}}", |
|
11 |
+ :agent_type => "{{agent.type}}", |
|
12 |
+ :agent_type_via_matching => "{{agent_info.type}}", |
|
12 | 13 |
}, |
13 | 14 |
:mode => "clean", |
14 | 15 |
:matchers => [ |
@@ -17,6 +18,11 @@ describe Agents::EventFormattingAgent do |
||
17 | 18 |
:regexp => "\\A(?<time>\\d\\d:\\d\\d [AP]M [A-Z]+)", |
18 | 19 |
:to => "pretty_date", |
19 | 20 |
}, |
21 |
+ { |
|
22 |
+ :path => "{{agent.type}}", |
|
23 |
+ :regexp => "\\A(?<type>.*)\\z", |
|
24 |
+ :to => "agent_info", |
|
25 |
+ }, |
|
20 | 26 |
], |
21 | 27 |
:skip_created_at => "false" |
22 | 28 |
} |
@@ -64,12 +70,13 @@ describe Agents::EventFormattingAgent do |
||
64 | 70 |
it "should handle Liquid templating in instructions" do |
65 | 71 |
@checker.receive([@event]) |
66 | 72 |
Event.last.payload[:message].should == "Received Some Lorem Ipsum from somevalue ." |
67 |
- Event.last.payload[:agent].should == "WeatherAgent" |
|
73 |
+ Event.last.payload[:agent_type].should == "WeatherAgent" |
|
68 | 74 |
end |
69 | 75 |
|
70 | 76 |
it "should handle matchers and Liquid templating in instructions" do |
71 | 77 |
@checker.receive([@event]) |
72 | 78 |
Event.last.payload[:subject].should == "Weather looks like someothervalue according to the forecast at 10:00 PM EST" |
79 |
+ Event.last.payload[:agent_type_via_matching].should == "WeatherAgent" |
|
73 | 80 |
end |
74 | 81 |
|
75 | 82 |
it "should allow escaping" do |
@@ -76,3 +76,39 @@ describe Event do |
||
76 | 76 |
end |
77 | 77 |
end |
78 | 78 |
end |
79 |
+ |
|
80 |
+describe EventDrop do |
|
81 |
+ def interpolate(string, event) |
|
82 |
+ event.agent.interpolate_string(string, event.to_liquid) |
|
83 |
+ end |
|
84 |
+ |
|
85 |
+ before do |
|
86 |
+ @event = Event.new |
|
87 |
+ @event.agent = agents(:jane_weather_agent) |
|
88 |
+ @event.payload = { |
|
89 |
+ 'title' => 'some title', |
|
90 |
+ 'url' => 'http://some.site.example.org/', |
|
91 |
+ } |
|
92 |
+ @event.save! |
|
93 |
+ end |
|
94 |
+ |
|
95 |
+ it 'should be created via Agent#to_liquid' do |
|
96 |
+ @event.to_liquid.class.should be(EventDrop) |
|
97 |
+ end |
|
98 |
+ |
|
99 |
+ it 'should have attributes of its payload' do |
|
100 |
+ t = '{{title}}: {{url}}' |
|
101 |
+ interpolate(t, @event).should eq('some title: http://some.site.example.org/') |
|
102 |
+ end |
|
103 |
+ |
|
104 |
+ it 'should be iteratable' do |
|
105 |
+ # to_liquid returns self |
|
106 |
+ t = "{% for pair in to_liquid %}{{pair | join:':' }}\n{% endfor %}" |
|
107 |
+ interpolate(t, @event).should eq("title:some title\nurl:http://some.site.example.org/\n") |
|
108 |
+ end |
|
109 |
+ |
|
110 |
+ it 'should have agent' do |
|
111 |
+ t = '{{agent.name}}' |
|
112 |
+ interpolate(t, @event).should eq('SF Weather') |
|
113 |
+ end |
|
114 |
+end |
@@ -20,7 +20,7 @@ shared_examples_for LiquidInterpolatable do |
||
20 | 20 |
|
21 | 21 |
describe "interpolating liquid templates" do |
22 | 22 |
it "should work" do |
23 |
- @checker.interpolate_options(@checker.options, @event.payload).should == { |
|
23 |
+ @checker.interpolate_options(@checker.options, @event).should == { |
|
24 | 24 |
"normal" => "just some normal text", |
25 | 25 |
"variable" => "hello", |
26 | 26 |
"text" => "Some test with an embedded hello", |
@@ -30,7 +30,7 @@ shared_examples_for LiquidInterpolatable do |
||
30 | 30 |
|
31 | 31 |
it "should work with arrays", focus: true do |
32 | 32 |
@checker.options = {"value" => ["{{variable}}", "Much array", "Hey, {{hello_world}}"]} |
33 |
- @checker.interpolate_options(@checker.options, @event.payload).should == { |
|
33 |
+ @checker.interpolate_options(@checker.options, @event).should == { |
|
34 | 34 |
"value" => ["hello", "Much array", "Hey, Hello world"] |
35 | 35 |
} |
36 | 36 |
end |
@@ -38,7 +38,7 @@ shared_examples_for LiquidInterpolatable do |
||
38 | 38 |
it "should work recursively" do |
39 | 39 |
@checker.options['hash'] = {'recursive' => "{{variable}}"} |
40 | 40 |
@checker.options['indifferent_hash'] = ActiveSupport::HashWithIndifferentAccess.new({'recursive' => "{{variable}}"}) |
41 |
- @checker.interpolate_options(@checker.options, @event.payload).should == { |
|
41 |
+ @checker.interpolate_options(@checker.options, @event).should == { |
|
42 | 42 |
"normal" => "just some normal text", |
43 | 43 |
"variable" => "hello", |
44 | 44 |
"text" => "Some test with an embedded hello", |
@@ -49,8 +49,8 @@ shared_examples_for LiquidInterpolatable do |
||
49 | 49 |
end |
50 | 50 |
|
51 | 51 |
it "should work for strings" do |
52 |
- @checker.interpolate_string("{{variable}}", @event.payload).should == "hello" |
|
53 |
- @checker.interpolate_string("{{variable}} you", @event.payload).should == "hello you" |
|
52 |
+ @checker.interpolate_string("{{variable}}", @event).should == "hello" |
|
53 |
+ @checker.interpolate_string("{{variable}} you", @event).should == "hello you" |
|
54 | 54 |
end |
55 | 55 |
end |
56 | 56 |
|